// Copyright (c) 2010 god6or@gmail.com under MIT license.

package ul.ownet

import collection.mutable.{ListBuffer,ArrayBuffer}
import java.util.concurrent.atomic.{AtomicBoolean}
import java.util.{Timer, TimerTask}
import actors.{Actor}

import ul.comm.{Serial, OneWire, OneWireSerial, OneWireThermometer}


// container for sensors current state
object Sensors {
    
    val sensors = new ArrayBuffer[Sensor]
    def count = sensors.length // number of sensors
    def names: List[String] = {
        (for (sensor <- sensors) yield sensor.name).toList
    }
    
    // returns sensor by serial number
    def bySerial( serialNumber: Long ): Option[Sensor] = {
        for (s <- sensors if s.serialNumber == serialNumber) return Some(s)
        return None
    }
    
    // returns true if sensor already present in list
    def contains( s: Sensor ): Boolean = sensors.exists( s.equalsTo( _ ) )


    case class SensorsDetect( ports: Seq[String] ) // sensors detection message
    case object SensorsDetectComplete // sensors detection completion message

    var sensorsDetectCompleteDetectors = new ArrayBuffer[ Actor ]
    var sensorsAreDetected = new AtomicBoolean(false) // sensors detection flag

    // sensor detecting thread
    object SensorsDetector extends Actor {
        start
        def act {
            loop {
                react {
                    case SensorsDetect(ports) =>
                        if (!sensorsAreDetected.get()) {
                            sensorsAreDetected.set(true)
                            val newSensors = new ArrayBuffer[Sensor]
                            for (pn <- ports) {
                                val port = Serial(pn)
                                if (port != null) {
                                    val ow = new OneWireSerial( port )
                                    for ((t,s,sn) <- ow.search() if (t == OneWire.types.DS18B20)) {
                                        newSensors += new Sensor(t, s, port.name, format("%X",s))
                                        //println("Found sensor: " + newSensors(newSensors.length-1).serialNumberHex)
                                    }
                                    ow.close
                                    port.close
                                }
                            }
                            // keep data of existing sensors
                            for (ns <- newSensors) {
                                for (s <- Sensors.sensors if s.equalsTo(ns)) s.copyTo(ns)
                            }
                            // copy found sensors to main storage
                            Sensors.sensors.clear
                            Sensors.sensors ++= newSensors
                            sensorsAreDetected.set( false ) // clear detection flag
                            
                            // send completion messages
                            for (detector <- sensorsDetectCompleteDetectors)
                                detector ! SensorsDetectComplete;
                        }
                    case _ =>
                }
            }
        }
    }


    case class SensorsRead( updateDB: Boolean = true ) // read sensors message
    case class SensorsReadComplete( updateDB: Boolean = true ) // sensors read completion message
    case class AddReadCompleteListener( listener: Actor )
    case class  SensorsPollStart( period: Int )
    case object SensorsPollStop
    

    var sensorsAreRead = new AtomicBoolean(false) // sensors reading flag
    var sensorsArePolled = new AtomicBoolean(false) // sensors polling flag

    // poll timer
    var sensorsPollTimer: Timer = null
    // poll timer task
    class sensorsPollTask extends TimerTask {
        override def run() {
            SensorsReader ! SensorsRead( true )
        }
    }

    // sensor reading thread
    object SensorsReader extends Actor {
        val readCompleteListeners = new ArrayBuffer[ Actor ]
        start
        def act {
            loop {
                react {
                    case AddReadCompleteListener( listener ) =>
                        readCompleteListeners += listener
                    case SensorsPollStart(period) =>
                        sensorsPollTimer = new Timer
                        sensorsPollTimer.scheduleAtFixedRate( new sensorsPollTask, 0, period * 1000L )
                    case SensorsPollStop =>
                        sensorsPollTimer.cancel()
                        sensorsPollTimer.purge()
                    case SensorsRead( updateDB ) =>
                        if (!sensorsAreRead.get()) {
                            sensorsAreRead.set( true )
                            val newTemp = new ArrayBuffer[Double] // temperatures array
                            var success = false
                            var pNames = Set[String]()
                            var port: Serial = null
                            for (sensor <- Sensors.sensors) {
                                //println("Reading sensor: " + sensor.serialNumberHex + " on port " + sensor.portName)
                                if ((port == null)||(port.name != sensor.portName)) {
                                    if (port != null) port.close
                                    port = Serial(sensor.portName)
                                }
                                if (port != null) {
                                    sensor.lastTemperature = sensor.temperature
                                    val ow = new OneWireSerial( port )
                                    //println("  port: " + port.name)
                                    if (sensor.rdData( ow )) {
                                        //println("  temperature: " + sensor.temperature)
                                        success = true
                                        pNames += sensor.portName
                                    } else sensor.temperature = Double.NaN
                                    ow.close
                                }
                            }
                            if (port != null) port.close
                            // send Start Conversion command to all sensors
                            for (pName <- pNames) {
                                val port = Serial(pName)
                                if (port != null) {
                                    val ow = new OneWireSerial( port )
                                    ow.wrRdPacket( List(OneWire.cmds.SKIP_ROM, OneWire.cmds.CONV_T_1822), 0, false, false )
                                    ow.close
                                    port.close
                                }
                            }
                            sensorsAreRead.set( false )

                            // send completion messages
                            for (listener <- readCompleteListeners)
                                listener ! SensorsReadComplete( updateDB );
                        }
                    case _ =>
                }
            }
        }
    }
}

